package com.geekydoo.commands;

import generated.TaskType;

import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;

import javax.xml.datatype.XMLGregorianCalendar;

import com.geekydoo.GeekyDooException;
import com.geekydoo.InputOutputHandler;
import com.geekydoo.InputValidation;
import com.geekydoo.XmlCalendarUtil;
import com.rits.cloning.Cloner;

/**
 * The purpose of this class is to support editing any of a task's attributes.
 * 
 * @author Wai Yau
 */
public class EditCommand implements Command {

	private static Logger logger = Logger.getLogger("geekydoo");

	// The list of all task objects managed by the program
	private List<TaskType> taskList;

	// List of all tasks as it was before a task was edited
	private static List<TaskType> preExecutionList;

	// Indicates whether or not a task was edited
	private boolean editExecuted = false;

	// Prompts printed by the edit command
	private static final String PROMPT_ID = "Task ID: ";
	private static final String PROMPT_ENTER_FIELDS = "Indicate the fields (separated by space):  ";
	private static final String PROMPT_NEW_LINK = "Enter a link: ";
	private static final String PROMPT_LINK_TO_EDIT =
			"Edit which link? (Enter link number, blank to cancel, or 'new' to add a link): ";
	private static final String PROMPT_NEW_VALUE_LINK = "New link: ";
	private static final String PROMPT_EDIT_LINK_VALUE = "New document link: ";

	// Allowed user input for which fields to change
	private static final String INPUT_TITLE = "((?i)title|(?i)name)";
	private static final String INPUT_DATE = "((?i)date|(?i)day)";
	private static final String INPUT_TIME = "((?i)time)";
	private static final String INPUT_CATEGORY = "((?i)category|(?i)categories)";
	private static final String INPUT_DESCRIPTION = "((?i)description)";
	private static final String INPUT_DOCUMENTLINK = "((?i)link|(?i)links)";

	// Field names used by the edit command
	private static final String FIELD_TITLE = "title";
	private static final String FIELD_DATE = "date";
	private static final String FIELD_TIME = "time";
	private static final String FIELD_CATEGORY = "category";
	private static final String FIELD_DESCRIPTION = "description";
	private static final String FIELD_DOCUMENTLINK = "link";

	// Marker indicating a field was not previously set
	private static final String VALUE_NOT_SET = "NONE";

	// Informational messages printed by the edit command
	private static final String INFO_LIST_EMPTY = "Task list is empty.";
	private static final String INFO_TASK_NOT_FOUND = "Task not found.";
	private static final String INFO_NO_ASSOCIATED_LINK = "No links associated with this task.";
	private static final String INFO_LINK_CHANGED = "Successfully changed the document links!";
	private static final String INFO_LINK_NOT_CHANGED = "Not changing any document links";
	private static final String INFO_LINK_ADDED = "Successfully added the document link!";
	private static final String INFO_LINK_NOT_ADDED = "Not adding any document links";
	private static final String INFO_LINK_REMOVED = "Successfully removed the document link!";
	private static final String INFO_SHOW_LINK_LIST = "Here's a list of the links that are present:";
	private static final String INFO_OLD_LINK = "Old document link: ";

	// Separators
	private static final String SEPARATOR_DATE = "-";
	private static final String SEPARATOR_TIME = ":";

	// Error messages printed by the edit command
	private static final String ERROR_INVALID_TITLE = "Please enter a non-empty title!";
	private static final String ERROR_INVALID_DATE = "Please enter a valid date!";
	private static final String ERROR_INVALID_TIME = "Please enter a valid time!";

	// User inputs having special meaning to the command
	private static final String EMPTY_STRING = "";
	private static final String NEW_STRING = "((?i)new|(?i)add)";

	public void execute(List<TaskType> tasks) throws GeekyDooException {
		taskList = tasks;
		Cloner cloner = new Cloner();
		preExecutionList = cloner.deepClone(taskList);

		if (taskList.size() == 0) {
			logger.info("The task list is empty so no tasks can be edited");
			InputOutputHandler.printToConsole(INFO_LIST_EMPTY, true, false);
			return;
		}

		String id = InputOutputHandler.getInputFromConsole(PROMPT_ID, true);
		TaskType theTask = getTaskById(id);
		if (theTask != null) {
			logger.info("Task " + id + " is going to be edited");
			editExecuted = true;
			processFields(theTask);
		}
		else {
			InputOutputHandler.printToConsole(INFO_TASK_NOT_FOUND, true, false);
		}
	}

	/**
	 * This method determines whether a task with ID equal to the id argument
	 * exists in the task list; if so, a reference to this task is returned.
	 * 
	 * @param id The ID of the task that should be returned
	 * @return the task with ID matching the id argument if it exists in the
	 *         list, null otherwise
	 */
	private TaskType getTaskById(String id) {
		TaskType theTask = null;
		for (int i = 0; i < taskList.size(); i++) {
			if (id.equals(taskList.get(i).getId())) {
				theTask = taskList.get(i);
				return theTask;
			}
		}
		return null;
	}

	/**
	 * This method reads from standard input the field names of a task that the
	 * user would like to change. For those field names which are recognized by
	 * the program, the user will be prompted for the new field values.
	 * 
	 * @param theTask The task whose field(s) should be updated
	 * @throws GeekyDooException If an error occurs while changing field values
	 */
	private void processFields(TaskType theTask) throws GeekyDooException {
		String fields = InputOutputHandler.getInputFromConsole(PROMPT_ENTER_FIELDS, true);
		String[] fieldsList = fields.split("\\s+");
		String theField = EMPTY_STRING;
		String oldValue = EMPTY_STRING;

		for (int i = 0; i < fieldsList.length; i++) {
			theField = fieldsList[i].toLowerCase();
			if (theField.matches(INPUT_TITLE)) {
				theField = FIELD_TITLE;
				oldValue = theTask.getTitle();
			}
			else if (theField.matches(INPUT_DATE)) {
				theField = FIELD_DATE;
				oldValue =
						theTask.getDate().getDay() + SEPARATOR_DATE + theTask.getDate().getMonth() + SEPARATOR_DATE
								+ theTask.getDate().getYear();
			}
			else if (theField.matches(INPUT_TIME)) {
				theField = FIELD_TIME;
				XMLGregorianCalendar time = theTask.getTime();
				if (time != null) {
					String hour = theTask.getTime().toString().split(SEPARATOR_TIME)[0];
					String minute = theTask.getTime().toString().split(SEPARATOR_TIME)[1];
					oldValue = hour + SEPARATOR_TIME + minute;
				}
				else {
					oldValue = VALUE_NOT_SET;
				}
			}
			else if (theField.matches(INPUT_CATEGORY)) {
				theField = FIELD_CATEGORY;
				oldValue = theTask.getCategory();
				if (oldValue.equals(EMPTY_STRING)) {
					oldValue = VALUE_NOT_SET;
				}
			}
			else if (theField.matches(INPUT_DESCRIPTION)) {
				theField = FIELD_DESCRIPTION;
				oldValue = theTask.getDescription();
				if (oldValue.length() >= 15) {
					oldValue = oldValue.substring(0, 15) + "...";
				}
				if (oldValue.equals(EMPTY_STRING)) {
					oldValue = VALUE_NOT_SET;
				}
			}
			else if (theField.matches(INPUT_DOCUMENTLINK)) {
				theField = FIELD_DOCUMENTLINK;
				editALink(theTask);
				continue;
			}
			else {
				InputOutputHandler.printToConsole(
						"\"" + fieldsList[i] + "\" is an invalid field and will not be used!", true, false);
				continue;
			}

			changeFieldValues(theField, oldValue, theTask);
		}
	}

	/**
	 * This method edits a task's document links.
	 * 
	 * @param theTask The task whose document links should be edited
	 */
	private void editALink(TaskType theTask) {
		List<String> linkList = theTask.getDocumentLinks();
		if (linkList.size() == 0) {
			InputOutputHandler.printToConsole(INFO_NO_ASSOCIATED_LINK, true, false);
			String newItem = InputOutputHandler.getInputFromConsole(PROMPT_NEW_LINK, true);
			if (newItem.equals(EMPTY_STRING)) {
				logger.info("The task's document links are not changed");
				InputOutputHandler.printToConsole(INFO_LINK_NOT_CHANGED, true, false);
			}
			else {
				linkList.add(newItem);
				logger.info("A document link has been added to the task");
				InputOutputHandler.printToConsole(INFO_LINK_ADDED, true, false);
			}
		}
		else {
			InputOutputHandler.printToConsole(INFO_SHOW_LINK_LIST, true, false);

			ListIterator<String> linkIterator = linkList.listIterator();
			int index = 0;
			while (linkIterator.hasNext()) {
				index++;
				InputOutputHandler.printToConsole(" " + index + ") " + linkIterator.next(), true, true);
			}

			String itemNo = EMPTY_STRING;
			while (!InputValidation.isValidLinkNumber(itemNo, index)) {
				itemNo = InputOutputHandler.getInputFromConsole(PROMPT_LINK_TO_EDIT, true);
				if (itemNo.equals(EMPTY_STRING) || itemNo.matches(NEW_STRING)) {
					break;
				}
			}
			changeLink(itemNo, linkList);
		}
	}

	/**
	 * This method edits an existing document link, adds a new one, or deletes
	 * an old one.
	 * 
	 * @param itemNo String representing what the user wants to do with the
	 *            link(s) or which link to edit
	 * @param linkList The list of document links
	 */
	private void changeLink(String itemNo, List<String> linkList) {
		if (itemNo.matches(NEW_STRING)) {
			String newItem = InputOutputHandler.getInputFromConsole(PROMPT_NEW_VALUE_LINK, true);
			if (!newItem.equals(EMPTY_STRING)) {
				linkList.add(newItem);
				logger.info("A document link has been added to the task");
				InputOutputHandler.printToConsole(INFO_LINK_ADDED, true, false);
			}
			else {
				logger.info("No link has been added");
				InputOutputHandler.printToConsole(INFO_LINK_NOT_ADDED, true, false);
			}
		}
		else if (itemNo.equals(EMPTY_STRING)) {
			logger.info("The task's document links are not changed");
			InputOutputHandler.printToConsole(INFO_LINK_NOT_CHANGED, true, false);
		}
		else {
			int itemNoInt = Integer.parseInt(itemNo);
			String linkItem = linkList.get(itemNoInt - 1);
			InputOutputHandler.printToConsole(INFO_OLD_LINK + linkItem, true, true);
			String newItem = InputOutputHandler.getInputFromConsole(PROMPT_EDIT_LINK_VALUE, true);
			linkList.remove(itemNoInt - 1);
			if (!newItem.equals(EMPTY_STRING)) {
				linkList.add(newItem);
				logger.info("The task's document links have been changed");
				InputOutputHandler.printToConsole(INFO_LINK_CHANGED, true, false);
			}
			else {
				logger.info("The task's document link has been removed");
				InputOutputHandler.printToConsole(INFO_LINK_REMOVED, true, false);
			}
		}
	}

	/**
	 * This method prompts the user for the updated field values for each field
	 * which he asked to change, and stores the updated values in the task.
	 * 
	 * @param theField The field whose value should be updated
	 * @param oldValue The original value of the field
	 * @param theTask The task containing the field whose value should be
	 *            updated
	 * @throws GeekyDooException If an error occurs while editing field values
	 */
	private void changeFieldValues(String theField, String oldValue, TaskType theTask) throws GeekyDooException {
		String fieldChangePrompt = theField + " (old value:  " + oldValue + " ) : ";
		String newValue = InputOutputHandler.getInputFromConsole(fieldChangePrompt, true);

		if (theField.equals(FIELD_TITLE)) {
			while (!InputValidation.isValidTitle(newValue)) {
				InputOutputHandler.printToConsole(ERROR_INVALID_TITLE, true, false);
				newValue = InputOutputHandler.getInputFromConsole(fieldChangePrompt, true);
			}
			theTask.setTitle(newValue);
			logger.info("The task's title is now: " + newValue);
		}

		else if (theField.equals(FIELD_DATE)) {
			while (!InputValidation.isValidDateString(newValue)) {
				InputOutputHandler.printToConsole(ERROR_INVALID_DATE, true, false);
				newValue = InputOutputHandler.getInputFromConsole(fieldChangePrompt, true);
			}

			newValue = XmlCalendarUtil.standardizeDateString(newValue);
			XMLGregorianCalendar date = XmlCalendarUtil.getCalendarFromDateString(newValue);
			theTask.setDate(date);
			logger.info("The task's date is now: " + date);
		}

		else if (theField.equals(FIELD_TIME)) {
			while (!InputValidation.isValidTimeString(newValue) && !newValue.equals(EMPTY_STRING)) {
				InputOutputHandler.printToConsole(ERROR_INVALID_TIME, true, false);
				newValue = InputOutputHandler.getInputFromConsole(fieldChangePrompt, true);
			}

			if (!newValue.equals(EMPTY_STRING)) {
				newValue = XmlCalendarUtil.standardizeTimeString(newValue);
				XMLGregorianCalendar time = XmlCalendarUtil.getCalendarFromTimeString(newValue);
				theTask.setTime(time);
			}
			else {
				theTask.setTime(null);
			}
			logger.info("The task's time is now: " + theTask.getTime());
		}

		else if (theField.equals(FIELD_CATEGORY)) {
			theTask.setCategory(newValue);
			logger.info("The task's categories are now: " + newValue);
		}

		else if (theField.equals(FIELD_DESCRIPTION)) {
			theTask.setDescription(newValue);
			logger.info("The task's description is now: " + newValue);
		}

		InputOutputHandler.printToConsole("Successfully changed " + theField + "!", true, false);
	}

	public boolean isListModified() {
		return editExecuted;
	}

	public List<TaskType> getPreExecutionList() {
		return preExecutionList;
	}

}
